package com.xiangxiao.rpan.storage.fastdfs;

import com.alibaba.fastjson.JSON;
import com.github.tobato.fastdfs.domain.fdfs.GroupState;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.AppendFileStorageClient;
import com.github.tobato.fastdfs.service.DefaultFastFileStorageClient;
import com.google.common.collect.Lists;
import com.xiangxiao.rpan.storage.constant.GlobalConsts;
import com.xiangxiao.rpan.storage.constant.ResponseCode;
import com.xiangxiao.rpan.storage.core.CacheManager;
import com.xiangxiao.rpan.storage.core.StorageProcessor;
import com.xiangxiao.rpan.storage.core.TransferByCacheManager;
import com.xiangxiao.rpan.storage.dto.StorePathDto;
import com.xiangxiao.rpan.storage.exception.StorageExcetion;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Future;

/**
 * @auther xiangxiao
 * @email 573768011@qq.com
 * @data 2023/8/18 15:39
 * 存储方式二--Fastdfs分布式文件存储
 */
@Slf4j
@Component(value = "fastDFSStorageProcessor")
@ConditionalOnProperty(prefix = "rpan.storage.processor", name = "type", havingValue = "com.xiangxiao.rpan.storage.fastdfs.FastDFSStorageProcessor")
public class FastDFSStorageProcessor implements StorageProcessor {
  private final Logger logger = LoggerFactory.getLogger(FastDFSStorageProcessor.class);

  private static final String SLASH = "/";
  private static final String COMMON_SEPARATOR = "__,__";
  @Autowired
  @Qualifier(value = "cacheManager")
  private CacheManager cacheManager;
  @Autowired
  private FastdfsStorageConfig fastdfsStorageConfig;
  @Autowired
  private AppendFileStorageClient appendFileStorageClient;
  @Autowired
  private DefaultFastFileStorageClient defaultFastFileStorageClient;
  @Autowired
  private FastTrackerClientWrapper fastTrackerClientWrapper;
  @Resource(name = "asyncTaskExecutor")
  private ThreadPoolTaskExecutor asyncTaskExecutor;
  @Autowired
  @Qualifier(value = "transferByCacheManager")
  private TransferByCacheManager transferByCacheManager;

  /**
   * 直接存储文件, 不分片
   * @param file 文件对象
   * @return 文件全路径 例如：group1/M00/00/00/wKhjZFsbgEGALzeKAFm1CNSDijc772.jpg
   */
  @Override
  public String store(MultipartFile file, String suffix) throws IOException {
    StorePath storePath = defaultFastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(), FilenameUtils.getExtension(file.getOriginalFilename()), null);
    return storePath.getFullPath();
  }

  /**
   * 转换单片文件为流
   * */
  private String convertSingleChunkFile(MultipartFile file, Integer chunkNumber, Integer totalChunks) throws IOException {
    long blockSize = 0;
    if (chunkNumber < totalChunks) { // 非最后一块分片,那就是固定大小
      blockSize = GlobalConsts.FILE_SPLIT_SIZE;
    } else { // 最后一块分片,大小要进行计算
      blockSize = file.getSize() - GlobalConsts.FILE_SPLIT_SIZE * (chunkNumber - 1);
    }

    return transferByCacheManager.convertMuitlFileToStream(file, chunkNumber, totalChunks, blockSize);
  }

  /**
   * 文件分片存储,大文件分片上传
   */
  @Override
  public String storeWithChunk(MultipartFile file, String identifier, Integer totalChunks, Integer chunkNumber,
                               Long totalSize, Long chunkSize, String redisFileChunkKey) throws IOException {
    StorePath storePath = null;
    String fileExtName = FilenameUtils.getExtension(file.getOriginalFilename());
    String cacheFileDateKey = GlobalConsts.FILE_DATA_KEY + ":" + identifier;
    String fileMsg = String.valueOf(cacheManager.get(redisFileChunkKey));

    Future<String> future = asyncTaskExecutor.submit(() -> convertSingleChunkFile(file, chunkNumber, totalChunks));
    try {
      String tempPath = future.get();
      File srcFile = new File(tempPath);

      FileInputStream inputStream = new FileInputStream(srcFile);
      if (chunkNumber == 1) {
        storePath = appendFileStorageClient.uploadAppenderFile(fastdfsStorageConfig.getGroup(), inputStream, chunkSize, fileExtName);
        cacheManager.put(cacheFileDateKey, JSON.toJSONString(storePath.getPath()), GlobalConsts.ONE_DAY_LONG);
      } else {
        StorePathDto storeMsg = JSON.parseObject(fileMsg, StorePathDto.class);
        Long offset = storeMsg.getCurrByte();

        String existFilePath = JSON.parseObject(String.valueOf(cacheManager.get(cacheFileDateKey)), String.class);
        appendFileStorageClient.modifyFile(fastdfsStorageConfig.getGroup(), existFilePath, inputStream, chunkSize, offset);
        inputStream.close();
      }
      if (storePath != null) {
        return storePath.getPath();
      } else {
        return "";
      }
    } catch (Exception e) {
      e.printStackTrace();
      throw new StorageExcetion("文件分片上传失败");
    }
  }

  @Override
  public String mergeChunks(String md5, int chunkCount, String suffix) throws IOException {
    String cacheFileDateKey = GlobalConsts.FILE_DATA_KEY + ":" + md5;
    String existFilePath = JSON.parseObject(String.valueOf(cacheManager.get(cacheFileDateKey)), String.class);
    System.out.println(existFilePath);
    String filePath = new StringBuffer(fastdfsStorageConfig.getGroup()).append(SLASH).append(existFilePath).toString();
    cacheManager.delete(cacheFileDateKey);
    return filePath;
  }

  @Override
  public String download(String absoluteUrl, HttpServletResponse response) throws IOException {
    return null;
  }

  @Override
  public String downloadWithChunk(byte[] bytes, HttpServletResponse response) throws IOException {
    return null;
  }

  private String getMaxFreeMBGroupName() {
    List<GroupState> groupList = Lists.newArrayList();
    int i = 0;
    //连接fastdfs失败时重试，最多重连5次
    do {
      try {
        //获取集群的所有储存group
        groupList = fastTrackerClientWrapper.listGroups();
        break;
      } catch (Exception e) {
        logger.error(e.getMessage(), e);
      }
      i++;
    } while (i < 5);
    //移除inactive的group
    groupList.removeIf(groupState -> groupState.getActiveCount() < 1);
    //选择一个可用空间最大的group
    Optional<GroupState> groupState = groupList.stream().max(Comparator.comparing(GroupState::getFreeMB));
    return groupState.orElseThrow(() -> new StorageExcetion("获取可储存的fastdfs group失败", ResponseCode.BIGFILE_UP_FAIL)).getGroupName();
  }
}